package org.knot.autojmx.core;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.management.Attribute;
import javax.management.AttributeChangeNotification;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.ReflectionException;
import javax.management.RuntimeOperationsException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.knot.autojmx.metadata.AutoJMXFieldMap;
import org.knot.autojmx.metadata.MethodField;
import org.knot.autojmx.metadata.NotificationField;
import org.knot.autojmx.metadata.PropertyField;



/**
 * DynamicMBeanImpl 
 * 
 * @author <a href="mailto:yuxiaoweimail@gmail.com">yuxiaowei</a>
 */
public class DynamicMBeanImpl extends NotificationBroadcasterSupport implements
        DynamicMBean {
	
	protected final Log logger = LogFactory.getLog(getClass());
	
	private Object bean ; 
	 
    private AutoJMXFieldMap m_configMap;

    
    private MBeanInfo m_mBeanInfo;

     
    private String m_className;

   
    private int m_sequenceNumber;

    
    public DynamicMBeanImpl(AutoJMXFieldMap properties , Object bean) {
    	this.m_configMap = properties;
        this.bean = bean;
        this. m_className = bean.getClass().getName();
        this.buildMBeanInfo();
    }

    public Object getAttribute(String arg0) throws AttributeNotFoundException,
        MBeanException, ReflectionException {
        PropertyField attribute = m_configMap.getPropertyFromName(arg0);
        if (attribute == null) {
            throw new AttributeNotFoundException(arg0 + " not found ");
        } else {
            try {
            	Field field = attribute.getField();
            	field.setAccessible(true);
				Object v = field.get(bean);
				if (v == null) {
	        		String type = attribute.getType();
	    	        if ("boolean".equalsIgnoreCase(type)) { v = Boolean.FALSE; }
	    	        else if ("byte".equalsIgnoreCase(type)) { v = new Byte((byte) 0); }
	    	        else if ("short".equalsIgnoreCase(type)) { v = new Short((short) 0); }
	    	        else if ("int".equalsIgnoreCase(type)) { v = new Integer(0); }
	    	        else if ("long".equalsIgnoreCase(type)) { v = new Long(0); }
	    	        else if ("float".equalsIgnoreCase(type)) { v = new Float(0); }
	    	        else if ("double".equalsIgnoreCase(type)) { v =new Double(0); }
	    	        else if ("char".equalsIgnoreCase(type)) { v = new Character((char) 0); }
	    	    }
			 return v;
			} catch (IllegalArgumentException e) {
				  throw new AttributeNotFoundException(arg0 + " not found  " + e.getMessage());
			} catch (IllegalAccessException e) {
				  throw new AttributeNotFoundException(arg0 + " not found  " + e.getMessage());
			}
        }
    }

    public AttributeList getAttributes(String[] attributeNames) {
        if (attributeNames == null) {
            throw new IllegalArgumentException(
                "attributeNames[] cannot be null");
        }
        AttributeList resultList = new AttributeList();
        for (int i = 0; i < attributeNames.length; i++) {
            PropertyField propertyField = (PropertyField) m_configMap
                .getPropertyFromField((String) attributeNames[i]);

            if (propertyField != null) {
            	Field field = propertyField.getField();
            	field.setAccessible(true);
            	Object value = null;
				try {
					value = field.get(bean);
				} catch (Exception e) {
					logger.error(" get " + attributeNames[i] + " object failed! " ,e);
					break;
				}  
                resultList.add(new Attribute(attributeNames[i], value));
            }
        }
        return resultList;
    }

    public MBeanInfo getMBeanInfo() {
        return m_mBeanInfo;
    }

    public Object invoke(String operationName, Object[] params,
            String[] signature) throws MBeanException, ReflectionException {
        MethodField method = m_configMap.getMethodFromName(operationName,
            signature);
        if (method != null) {
            Method methodCall = method.getMethod();
            try {   
            	 Object result =  methodCall.invoke(bean, params);
            	 logger.info("method called, bean name:" + bean.getClass().getName() + " method name:" + methodCall.getName());
            	 return result;
            } catch (IllegalAccessException e) {
                throw new ReflectionException(e);
            } catch (InvocationTargetException e) {
                throw new MBeanException(e);
            }
        } else {
            throw new ReflectionException(new NoSuchMethodException(
                operationName), "Cannot find the operation " + operationName
                    + " in " + m_className);
        }
    }

    public void setAttribute(Attribute attribute)
        throws AttributeNotFoundException, InvalidAttributeValueException,
        MBeanException, ReflectionException {

        if (attribute == null) {
            throw new RuntimeOperationsException(new IllegalArgumentException(
                "Attribute cannot be null"), "Cannot invoke a setter of "
                    + m_className + " with null attribute");
        }
        String name = attribute.getName();
        Object value = attribute.getValue();

        if (name == null) {
            throw new RuntimeOperationsException(new IllegalArgumentException(
                "Attribute name cannot be null"),
                "Cannot invoke the setter of " + m_className
                        + " with null attribute name");
        }
        PropertyField propertyField = (PropertyField) m_configMap
            .getPropertyFromName(name);
        if (propertyField == null) {
            throw new AttributeNotFoundException("Attribute " + name
                    + " not found in " + m_className);
        }
        if (!propertyField.isWritable()) {
            throw new InvalidAttributeValueException("Attribute " + name
                    + " can not be set");
        }
        try {
        	Field field = propertyField.getField();
        	field.setAccessible(true);
			field.set(bean, value); 
			logger.info("property changed , bean name:" + bean.getClass().getName() + " property name:" + field.getName());
        } catch (Exception e) {
            throw new InvalidAttributeValueException(
                "Set attribute " + name + " failure! ");
        }
    }

  
    public AttributeList setAttributes(AttributeList attributes) {
        if (attributes == null) {
            throw new RuntimeOperationsException(new IllegalArgumentException(
                "AttributeList attributes cannot be null"),
                "Cannot invoke a setter of " + m_className);
        }
        AttributeList resultList = new AttributeList();

        if (attributes.isEmpty()) {
            return resultList;
        }

        for (Iterator i = attributes.iterator(); i.hasNext();) {
            Attribute attr = (Attribute) i.next();
            try {
                setAttribute(attr);
                String name = attr.getName();
                Object value = getAttribute(name);
                resultList.add(new Attribute(name, value));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return resultList;
    }

    private void buildMBeanInfo() {

        MBeanAttributeInfo[] dAttributes = null;

        if (m_configMap == null) {
            return;
        }

        String dDescription = m_configMap.getDecription();

        if (m_configMap.getProperties() != null) {
            List < MBeanAttributeInfo > lAttributes = null;
            lAttributes = new ArrayList < MBeanAttributeInfo >();

            Iterator < PropertyField > iterator = m_configMap.getProperties()
                .iterator();
            while (iterator.hasNext()) {
                PropertyField propertyField = iterator.next();
                lAttributes.add(new MBeanAttributeInfo(propertyField.getName(),
                    propertyField.getType(), propertyField.getDescription(),
                    propertyField.isReadable(), propertyField.isWritable(),
                    false));
            }
            dAttributes = lAttributes
                .toArray(new MBeanAttributeInfo[lAttributes.size()]);
        }

        MBeanOperationInfo[] dOperations = null;
        if (m_configMap.getMethods() != null) {

            List < MBeanOperationInfo > lOperations = new ArrayList < MBeanOperationInfo >();

            Iterator < MethodField[] > iterator = m_configMap.getMethods()
                .iterator();
            while (iterator.hasNext()) {
                MethodField[] method = iterator.next();
                for (int i = 0; i < method.length; i++) {
                    lOperations.add(new MBeanOperationInfo(method[i].getName(),
                        method[i].getDescription(), method[i].getParams(),
                        method[i].getReturnType(), MBeanOperationInfo.UNKNOWN));
                }
                dOperations = lOperations
                    .toArray(new MBeanOperationInfo[lOperations.size()]);
            }
        }

        MBeanNotificationInfo[] dNotification = new MBeanNotificationInfo[0];
        if (m_configMap.getMethods() != null) {

            List < MBeanNotificationInfo > lNotifications = new ArrayList < MBeanNotificationInfo >();

            Iterator < NotificationField > iterator = m_configMap
                .getNotifications().iterator();
            while (iterator.hasNext()) {
                NotificationField notification = iterator
                    .next();
                lNotifications.add(notification.getNotificationInfo());
            }
            dNotification = lNotifications
                .toArray(new MBeanNotificationInfo[lNotifications.size()]);
        }

        m_mBeanInfo = new MBeanInfo(this.m_className, dDescription,
            dAttributes, null, // No constructor
            dOperations, dNotification);
    }

    public MBeanNotificationInfo[] getNotificationInfo() {
        MBeanNotificationInfo[] dNotification = new MBeanNotificationInfo[0];
        if (m_configMap.getMethods() != null) {

            List < MBeanNotificationInfo > lNotifications = new ArrayList < MBeanNotificationInfo >();

            Iterator < NotificationField > iterator = m_configMap
                .getNotifications().iterator();
            while (iterator.hasNext()) {
                NotificationField notification = iterator
                    .next();
                lNotifications.add(notification.getNotificationInfo());
            }
            dNotification = lNotifications
                .toArray(new MBeanNotificationInfo[lNotifications.size()]);
        }
        return dNotification;
    }

    public void sendNotification(String msg, String attributeName,
            String attributeType, Object oldValue, Object newValue) {

        long timeStamp = System.currentTimeMillis();

        if ((newValue == null  && oldValue == null)
        		|| (newValue != null  && newValue.equals(oldValue))) {
            return;
        }
        m_sequenceNumber++;
        Notification notification = new AttributeChangeNotification(this,
            m_sequenceNumber, timeStamp, msg, attributeName, attributeType,
            oldValue, newValue);
        sendNotification(notification);
        logger.info("Notification sented");
    }
}
